package edu.buaa.resourceManager.vo;

import com.google.common.io.ByteStreams;
import edu.buaa.resourceManager.helper.Utils;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Created by song on 2017/6/4 0004.
 */
public class EZProjectFile
{
    private static Logger log = Utils.getDevLogger();

    protected float frameRate;
    protected List<String> materials = new ArrayList<>();
    protected Map<Long, String> comments = new HashMap<Long, String>();
    protected String projectFile;

    private EZProjectFile(ZipFile zipFile) throws IOException {
        for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
            ZipEntry entry = (ZipEntry) e.nextElement();
//            System.out.println(entry.getName());
            if(entry.getName().toUpperCase().endsWith(".EPJ")){
                extractComment(zipFile.getInputStream(entry));
            }else if(entry.getName().equals("MaterialInfo.xml")){
                extractMaterial(zipFile.getInputStream(entry));
            }
        }
        zipFile.close();
    }

    protected EZProjectFile(){}

    protected void extractComment(InputStream extra) {
        try {
            ByteBuffer in = ByteBuffer.wrap(ByteStreams.toByteArray(extra)).asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN);
            while(true) {
                readUntil(in, b("Marker"));
                long currentFramePos = getFramePos(in);
                boolean commentIsUTF16 = isCommentUTF16encode(in);
                int commentLen = getCommentLen(in);
                String comment = readComment(in, commentIsUTF16, commentLen);
//                log.debug("framePos:{} {} len:{} {}", currentFramePos, comment, commentLen, commentIsUTF16);
                this.comments.put(currentFramePos, comment);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotFoundException ignore) {

        }
    }

    private String readComment(ByteBuffer in, boolean commentIsUTF16, int commentLen) {
        byte[] content = new byte[commentLen];
        in.get(content);
        if(commentIsUTF16){
            return new String(content, Charset.forName("UTF-16LE"));
        }else{
//            log.debug("{}", Utils.byteArray2Hex(content));
            return new String(content, Charset.forName("UTF-8"));
        }
    }

    private int getCommentLen(ByteBuffer in) throws NotFoundException {
        readUntil(in, b("Comment"));
        byte firstByte = in.get();
        if((firstByte & 0b10000000) == 0b00000000){
            return firstByte;
        }else{
            byte secondByte = in.get();
            return (firstByte & 0b01111111) + secondByte*128;
        }
    }

    private boolean isCommentUTF16encode(ByteBuffer in) {
        return in.get()==0x5C;
    }

    private long getFramePos(ByteBuffer in) throws NotFoundException {
        readUntil(in, b("Pos"));
        //                log.debug("position: {}", in.position());
//                log.debug("{} {} {}", in.get(in.position()-3), in.get(in.position()-2), in.get(in.position()-1));
//                log.debug("{} {} {}", in.get(in.position()), in.get(in.position()+1), in.get(in.position()+2));
        byte length = in.get();
//                byte[] lenArr = new byte[2];
//                lenArr[0] = in.get(in.position());
//                lenArr[1] = in.get(in.position()+1);
//                log.debug("{}", lenArr);
//                log.debug("{}", Utils.byteArray2Hex(lenArr));
        long framePos;
        if(length==1){
            framePos = in.get();
        }else if(length==2){
//                    log.debug("{}", in.get());
//                    log.debug("{}", in.get());
            framePos = in.getShort();
//            log.debug("position: {}", in.position());
        }else if(length==4) {
            framePos = in.getInt();
        }else{
            log.debug("length: {}", length);
            throw new RuntimeException("length > 2");
        }
//        log.debug("{}", framePos);
        return framePos;
    }

    protected void readUntil(ByteBuffer in, byte[] b) throws NotFoundException {
        if(in.remaining()<b.length) throw new NotFoundException();
        int j=0;
        while(in.remaining()>0){
            int k=in.position();
            while(j<b.length && in.get()==b[j]){
                k++;
                j++;
            }
            if(j==b.length){
                return;
            }else{
                in.position(k+1);
                j=0;
            }
        }
        throw new NotFoundException();
    }

    // ascii code to byte array.
    protected byte[] b(String byteStr) {
//        Utils.getDevLogger().debug("{}", byteStr.getBytes(Charset.forName("utf-8")));
        return byteStr.getBytes(Charset.forName("ascii"));
    }

    protected void extractMaterial(InputStream inputStream)  {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
//        ByteArrayInputStream input =  new ByteArrayInputStream(inputStream));
            Document doc = builder.parse(new InputSource(new InputStreamReader(inputStream, "UTF-8")));
            NodeList projectFiles = doc.getElementsByTagName("materialInfo:projectFile");
            this.projectFile = projectFiles.item(0).getTextContent();
//            Utils.getDevLogger().debug("project file: {}", this.projectFile);
            NodeList materials = doc.getElementsByTagName("materialInfo:materialFile");
            for (int i = 0; i < materials.getLength(); i++) {
                Node material = materials.item(i);
                this.materials.add(material.getTextContent());
//                Utils.getDevLogger().debug("material: {}", material.getTextContent());
            }
        }catch (ParserConfigurationException| IOException| SAXException e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    public Map<Long, String> getComment(){
        return this.comments;
    }

    public List<String> getDependFile(){
        return this.materials;
    }

    protected class NotFoundException extends Throwable {

    }

    public static EZProjectFile getEZProjectFile(File ezpFile) throws IOException {
        ZipFile zipFile = new ZipFile(ezpFile);
        for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
            ZipEntry entry = (ZipEntry) e.nextElement();
            System.out.println(entry.getName());
            if(entry.getName().equals("MaterialInfo.xml")){
                log.debug("EDIUS 7 file");
                return new EZProjectFile(zipFile);
            }
        }
        log.debug("EDIUS 5 file");
        return new EZProjectFileOld(zipFile);
    }

}
